from __future__ import with_statement
import types

import transaction

import time
from crv.model import DBSession, metadata
from crv.model.crvmodel import *

import logging
log = logging.getLogger(__name__)

from threading import Thread, Lock
from collections import deque

from pygraph.classes.Digraph import digraph
from pygraph.algorithms.minmax import *


class StateMachineMgr(object):
    """
    @author Yushu Yao
    @version 0.1
    """

    def __init__(self):
        self.__sg=digraph()
        self.initialized=False
        self.shortestPath={}

    def initialize(self,SC,SCname):
        #have to call this before calling advanceStates
        #here we build
        self.shortestPath={}
        for i in SCname.keys():
            (tr,l)=shortest_path(self.__sg,i)
            for j in SC.canTarget:
#                print "finding shortest path from %s to %s"%(SCname[i],SCname[j])
                f=i
                t=j
                path=[]
                while f!=t:
                    if t in tr.keys():
                        path.append(t)
                        t=tr[t]
                    else:
                        #already at root of the tree
                        t=f
                        path.append(t)
                path=path+[t]
                path.reverse()
                self.shortestPath[(i,j)]=path
        
        self.initialized=True
        
        
    def addNodes(self,nodes):
        self.__sg.add_nodes(nodes)

    def addEdge(self,f,t):
        self.__sg.add_edge(f,t)

    def setFunc_checkState(self, node, func):
        if not isinstance(func, types.FunctionType) and not isinstance(func, types.MethodType):
            raise RuntimeError('StateMachineMgr::setFunc_checkState:: %s is not a FunctionType nor MethodType'%str(func))
        
        attrs=self.__sg.node_attributes(node)
        for iattr in attrs:
            if iattr[0]=="checkState":
                iattr[1]=func
                return

        self.__sg.add_node_attribute(node,("checkState",func))

    def getFunc_checkState(self, node):
        attrs=self.__sg.node_attributes(node)
        for iattr in attrs:
            if iattr[0]=="checkState":
                return iattr[1]
        return None

    def setFunc_canAdvance(self,f, t, func):
        if not isinstance(func, types.FunctionType) and not isinstance(func, types.MethodType):
            raise RuntimeError('StateMachineMgr::setFunc_canAdvance:: %s is not a FunctionType nor MethodType'%str(func))
        if not t in self.__sg.neighbors(f):
            raise RuntimeError('StateMachineMgr::setFunc_canAdvance:: no edge points from %s to %s'%(str(f),str(t)))
        attrs=self.__sg.edge_attributes(f,t)
        for iattr in attrs:
            if iattr[0]=="canAdvance":
                iattr[1]=func
                return

        self.__sg.add_edge_attribute(f,t,("canAdvance",func))
        
    def getFunc_canAdvance(self,f, t):
        attrs=self.__sg.edge_attributes(f,t)
        for iattr in attrs:
            if iattr[0]=="canAdvance":
                return iattr[1]
        return None
        
    def setFunc_doAdvance(self,f,t,func):
        if not isinstance(func, types.FunctionType) and not isinstance(func, types.MethodType):
            raise RuntimeError('StateMachineMgr::setFunc_doAdvance:: %s is not a FunctionType nor MethodType'%str(func))
        if not t in self.__sg.neighbors(f):
            raise RuntimeError('StateMachineMgr::setFunc_doAdvance:: no edge points from %s to %s'%(str(f),str(t)))
        attrs=self.__sg.edge_attributes(f,t)
        for iattr in attrs:
            if iattr[0]=="doAdvance":
                iattr[1]=func
                return

        self.__sg.add_edge_attribute(f,t,("doAdvance",func))
        
    def getFunc_doAdvance(self,f, t):
        attrs=self.__sg.edge_attributes(f,t)
        for iattr in attrs:
            if iattr[0]=="doAdvance":
                return iattr[1]
        return None
        
    def callFunc_checkState(self,state,obj):
        #log.debug("StateMachineMgr::callFunc_checkState:: calling checkState for state=%s obj=%s" % (obj.SCname[state],obj.getAttr("name")))
        if self.getFunc_checkState(state):
            return self.getFunc_checkState(state)(obj)
        else:
            raise RuntimeError("StateMachineMgr::callFunc_checkState:: function checkState for state=%s not defined for class %s"%(obj.SCname[state],obj.__class__.__name__))

    def callFunc_canAdvance(self,stateF, stateT,obj):
        if self.getFunc_canAdvance(stateF,stateT):
            #log.debug("StateMachineMgr::callFunc_canAdvance:: calling existing %d %d %s"%(stateF,stateT,str(obj.SCname)))
#            #log.debug("StateMachineMgr::callFunc_canAdvance:: calling existing canAdvance for state=%s,%s obj=%s"%(obj.SCname[stateF],obj.SCname[stateT],obj.getAttr("name")))
            return self.getFunc_canAdvance(stateF,stateT)(obj)
        else:
            #log.debug("StateMachineMgr::callFunc_canAdvance:: function canAdvance for state=%s,%s not defined for class %s, returning False"%(obj.SCname[stateF],obj.SCname[stateT],obj.__class__.__name__))
            return False
       
    def callFunc_doAdvance(self,stateF, stateT,obj):
        #log.debug("StateMachineMgr::callFunc_doAdvance:: calling doAdvance for state=%s,%s obj=%s"%(obj.SCname[stateF],obj.SCname[stateT],obj.getAttr("name")))
        if self.getFunc_doAdvance(stateF,stateT):
            self.getFunc_doAdvance(stateF,stateT)(obj)
        else:
            #log.debug("StateMachineMgr::callFunc_doAdvance:: function doAdvance for state=%s,%s not defined for class %s, Doing nothing"%(obj.SCname[stateF],obj.SCname[stateT],obj.__class__.__name__))
            pass
       
    def advanceStates(self,obj):
        #log.debug("StateMachineMgr::advanceStates:: called")
        if not self.initialized:
            raise RuntimeError("StateMachineMgr::advanceStates:: not yet initialized")
        
        #the obj must have field currentSC, targetSC

        if not self.callFunc_checkState(obj.currentSC,obj):
            #log.debug("StateMachineMgr::advanceStates:: looking into obj.SCname.keys=%s"%(" ".join(obj.SCname[i] for i in obj.SCname.keys())))
            goodState=None
            for istate in obj.SCname.keys():
                if istate!=None and self.callFunc_checkState(istate,obj):
                    goodState=istate
                    break
            if goodState!=None:
                #log.debug("StateMachineMgr::advanceStates:: found goodState=%s"%(obj.SCname[goodState]))
                obj.currentSC=goodState
            else:
                raise RuntimeError("StateMachineMgr::advanceStates:: Panic, object %s in a state that can't be identified."%obj.getAttr("name"))
                
        if obj.currentSC==obj.targetSC:
#            #log.debug("StateMachineMgr::advanceStates:: currentSC=%s is valid, currentSC is targetSC=%s, returning..."%(obj.SCname[obj.currentSC],obj.SCname[obj.targetSC]))
            return
        else:
#            #log.debug("StateMachineMgr::advanceStates:: currentSC=%s is valid, currentSC isn't targetSC=%s, continue..."%(obj.SCname[obj.currentSC],obj.SCname[obj.targetSC]))
            #see below
            pass


        if not obj.targetSC in obj.SC.canTarget:
            #log.debug("StateMachineMgr::advanceStates:: obj.targetSC %s is not target-able (canTarget=%s)"%
#                      (obj.SCname[obj.targetSC],",".join(obj.SCname[i] for i in obj.SC.canTarget)))
            return
        
        #FindNextStateTowardTarget
        try:
            nextSC=self.shortestPath[(obj.currentSC,obj.targetSC)][1]
            #log.debug("StateMachineMgr::advanceStates:: nextSC=%s"%obj.SCname[nextSC])
        except:
            raise RuntimeError("StateMachineMgr::advanceStates:: no path from %s to %s" % (obj.SCname[obj.currentSC],obj.SCname[obj.targetSC]))
        
        if self.callFunc_checkState(nextSC,obj):
#            #log.debug("StateMachineMgr::advanceStates:: checkState for %s returns true, setting currentSC to it"%obj.SCname[nextSC])
            obj.currentSC=nextSC
            return
#        #log.debug("StateMachineMgr::advanceStates:: checkState for %s returns false, calling canAdvance..."%obj.SCname[nextSC])
        
        #canadvance(curstate, nextstate)
        if self.callFunc_canAdvance(obj.currentSC,nextSC,obj):
 #           #log.debug("StateMachineMgr::advanceStates:: can advance from %s to %s, calling doAdvance"%(obj.SCname[obj.currentSC],obj.SCname[nextSC]))
            self.callFunc_doAdvance(obj.currentSC,nextSC,obj)
            return

#        #log.debug("StateMachineMgr::advanceStates:: %d %d %s"%(obj.currentSC, nextSC, str(obj.SCname)))
#        #log.debug("StateMachineMgr::advanceStates:: can not advance from %s to %s, looping over neighbors"%(obj.SCname[obj.currentSC],obj.SCname[nextSC]))

        canAdvanceTo=None
        for ineighbor in self.__sg.neighbors(obj.currentSC):
            if self.callFunc_canAdvance(obj.currentSC,ineighbor,obj):
                #log.debug("StateMachineMgr::advanceStates:: can advance from %s to %s, setting canAdvanceTo"%(obj.SCname[obj.currentSC],obj.SCname[ineighbor]))
                canAdvanceTo=ineighbor
                break
            else:
                #log.debug("StateMachineMgr::advanceStates:: can not advance from %s to %s, continue..."%(obj.SCname[obj.currentSC],obj.SCname[ineighbor]))
                pass

        if canAdvanceTo:
            #log.debug("StateMachineMgr::advanceStates:: can advance from %s to %s, calling doAdvance"%(obj.SCname[obj.currentSC],obj.SCname[canAdvanceTo]))
            self.callFunc_doAdvance(obj.currentSC,canAdvanceTo,obj)
    
        
